package nachos.threads;

import nachos.machine.*;

import java.util.Random;

/**
 * A Tester for the Communicator class
 */
public class CommunicatorTest {

    /**
     * CommunicatorTest class, which has threads do a bunch of "random"
     * rendez-vous, producing output that indicates whether communicating
     * threads do indeed rendez-vous or not.
     */

    /**
     * RVThread class, which implements a thread that 
     * does a bunch of rendez-vous.
     */
    private static class RVThread implements Runnable {

        /* Constructor */
	RVThread(String name, Communicator comm, boolean isSpeaker, 
                 int howMany, Communicator commFinished, Random rng) {
	    this.name = name;
	    this.comm = comm;
	    this.isSpeaker = isSpeaker;
	    this.howMany = howMany;
	    this.commFinished = commFinished;
	    this.rng = rng;
	}

        /** Method to generate a random number of ticks that
          * needs to be spent waiting before attempting a 
          * speak/listen actions. The number of ticks is
          * generated to be between minDelay and maxDelay, inclusive
          */
        private int randomDelay() {
          return minDelay+rng.nextInt(1+maxDelay - minDelay)-1; 
        }

        /** Method to generate a random word that a speaker
          * will speak through the Communicator.
          */
        private int randomWord() {
          return rng.nextInt(50); /* between 0 and 50 */
        }
	
        /** run() method for the RVThread. This method loops howMany
          * times. During this loop the thread waits some random
          * amount of time, then speaks or listen, depending on
          * whether this.isSpeaker is sets to true or false.
          */
	public void run() {

          System.out.println("** "+name+" begins.");

	  /* Main loop  */
	  for (int i=0; i < howMany; i++) {
	    /* Sleep for some random delay */
	    int randomDelay = randomDelay();
	    System.out.println("** "+name+": Sleeping for "+randomDelay+
                               " (i.e., until time="+
                               (randomDelay+Machine.timer().getTime())+")");
	    ThreadedKernel.alarm.waitUntil(randomDelay);
	    System.out.println("** "+name+": Done sleeping! (time="+
                                Machine.timer().getTime()+")");
	    
	    if (isSpeaker) {
	      /* I am a speaker and I speak my word */
              int randomWord = randomWord();
	      System.out.println("** "+name+": Speaking "+randomWord+
                        " (time="+Machine.timer().getTime()+")");
              comm.speak(randomWord);
	      System.out.println("** "+name+": Spoke and Returned (time="+
                          Machine.timer().getTime()+")");
            } else {
	      /* I am a listener and I listen */
	      System.out.println("** "+name+": Listening (time="+
                                  Machine.timer().getTime()+")");
              int word = comm.listen();
	      System.out.println("** "+name+": Listened and got "+word+
                          " (time="+Machine.timer().getTime()+")");
            }
          }

	  /* Exits and signals it to the main thread */
	  commFinished.speak(-1);
          System.out.println("** "+name+" exits.");
	}

	/* My name */
	private String name;
	/* The Communicator for speaking/listening */
	private Communicator comm;
	/* True if I am a speaker, false if I am a listener */
	private boolean isSpeaker;
	/* The number of iterations */
	private int howMany;
	/* The Communicator for signaling that I am done */
	private Communicator commFinished;
	/* Random number generator */
	private Random rng;
    }

    /**
     * Tests whether this module is working.
     */
    public static void runTest() {
	System.out.println("**** Communicator testing begins ****");

	/* Create a random number generator */
	Random rng = new Random();

    	/* Create the communicator on which RVThreads communicate */
    	Communicator comm = new Communicator();

    	/* Create the communicator for listening to terminations */
    	Communicator commFinished = new Communicator();

        /* Create rendezvous threads and fork them*/
        KThread rvs[] = new KThread[numRVThreads];
	for (int i=0; i < numRVThreads; i++) {
	  if (i%2 == 0) {
	    /* Creating a speaker */
	    rvs[i] = new KThread(new RVThread("RV-Thread(speaker) #"+i,
                             comm,true,howMany,commFinished,rng));
            rvs[i].setName("RV-Thread(speaker) #"+i);
          } else {
	    /* Creating a listener */
	    rvs[i] = new KThread(new RVThread("RV-Thread(listener) #"+i,
                             comm,false,howMany,commFinished,rng));
            rvs[i].setName("RV-Thread(listener) #"+i);
          }
	  /* fork() */
          rvs[i].fork();
 	}	

	/* Wait for all threads to signal that they're done,
         * which is done via a Communicator for good measures */
	for (int i=0; i < numRVThreads; i++) {
          commFinished.listen();
          System.out.println("Acknowledged one thread exit.");
        }

	System.out.println("**** Communicator testing ends ****");
	
    }

	

    /* Number of Threads. Must be EVEN!! */
    private static final int numRVThreads = 8;

    /* Number of RV actions per thread */
    private static final int howMany = 100;

    /* Bounds on delay between attempts to speak/listen */
    private static final int minDelay = 2;
    private static final int maxDelay = 8;
}
